package com.xiaozhen.blog.service.impl;

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaozhen.blog.entity.LoginTicket;
import com.xiaozhen.blog.entity.User;
import com.xiaozhen.blog.mapper.UserMapper;
import com.xiaozhen.blog.service.UserService;
import com.xiaozhen.blog.utils.*;

import static com.xiaozhen.blog.utils.CommunityConstant.*;

import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @create 2023-04-22 10:51
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private MailClient mailClient;

    @Autowired
    private TemplateEngine templateEngine;

    @Value("${blog.path.domain}")
    private String domain;

    @Value("${server.servlet.context-path}")
    private String contextPath;

    /**
     * 注册
     *
     * @return
     */
    public Map<String, Object> register(User user) {
        Map<String, Object> map = new HashMap<>();
        // 空值处理
        if (user == null) {
            System.out.println("参数不能为空！");
        }
        if ("".equals(user.getUsername()) || user.getUsername() == null) {
            map.put("usernameMsg", "账号不能为空！");
            return map;
        }
        if ("".equals(user.getPassword()) || user.getPassword() == null) {
            map.put("passwordMsg", "密码不能为空！");
            return map;
        }
        if ("".equals(user.getEmail()) || user.getEmail() == null) {
            map.put("emailMsg", "邮箱不能为空！");
            return map;
        }
        // 验证账号
        LambdaQueryWrapper<User> usernameWrapper = new LambdaQueryWrapper<>();
        usernameWrapper.eq(User::getUsername, user.getUsername());
        Integer usernameCount = userMapper.selectCount(usernameWrapper);
        if (usernameCount == 1) {
            map.put("usernameMsg", "账号已存在！");
            return map;
        }
        // 验证邮箱
        LambdaQueryWrapper<User> emailWrapper = new LambdaQueryWrapper<>();
        emailWrapper.eq(User::getEmail, user.getEmail());
        Integer emailCount = userMapper.selectCount(emailWrapper);
        if (emailCount == 1) {
            map.put("emailMsg", "该邮箱已被注册！");
            return map;
        }
        // 注册用户
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encode = passwordEncoder.encode(user.getPassword());
        user.setPassword(encode);
        user.setActivationCode(SecureUtil.simpleUUID());
        user.setHeaderUrl("https://tupian.qqw21.com/article/UploadPic/2020-5/20205622141367241.jpg");
        // Date 对象转换为时间戳，成为用户默认昵称后缀
        Date date = new Date();
        String times = String.valueOf(date.getTime());
        user.setNickname("小圳用户_" + times);

        // 激活邮件
        Context context = new Context();
        context.setVariable("email", user.getEmail());
        // http://localhost:8081/blog/activation/用户id/激活码
        // http://l47.120.10.227:8081/blog/activation/用户id/激活码

        String url = domain + contextPath + "/activation/" + user.getId() + "/" + user.getActivationCode();
        System.out.println(url);
        context.setVariable("url", url);
        String content = templateEngine.process("activation", context);
        mailClient.sendMail(user.getEmail(), "激活账号", content);
        return map;
    }

    /**
     * 判断是否激活成功
     *
     * @param userId
     * @param code
     * @return
     */
    public int activation(Long userId, String code) {
        User user = userMapper.selectById(userId);
        if (user.getStatus() == 1) {
            return ACTIVATION_REFEAT;
        } else if (user.getActivationCode().equals(code)) {
            user.setStatus(1);
            userMapper.updateById(user);
            return ACTIVATION_SUCCESS;
        } else {
            return ACTIVATION_FAILURE;
        }
    }

    @Override
    public User selectByName(String username) {
        return null;
    }

    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 登录
     *
     * @param username
     * @param password
     * @param expiredSeconds
     * @return
     */
    public Map<String, Object> login(String username, String password, int expiredSeconds) {
        // AuthenticationManager authenticate 进行用户认证
        // 把用户名和密码封装成 authentication 对象
        // 获取认证对象
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        // 认证
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        // 如果认证没通过，给出对应的提示
        if (Objects.isNull(authenticate)) {
            throw new RuntimeException("登录失败");
        }
        // 如果认证通过，使用 userId 生成一个 jwt
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        String jwt = JwtUtil.createJWT(userId);

        // 把完整的用户信息存入 redis ，userId 作为 key
        Map<String, Object> map = new HashMap<>();
        map.put("token", jwt);
        redisCache.setCacheObject("login:" + userId, JSON.toJSONString(loginUser));

        // 空值的处理
        if (StringUtils.isBlank(username)) {
            map.put("usernameMsg", "账号不能为空！");
            return map;
        }
        if (StringUtils.isBlank(password)) {
            map.put("passwordMsg", "密码不能为空！");
            return map;
        }
        // 验证账号
        User user = userMapper.selectByUsername(username);
        if (user == null) {
            map.put("usernameMsg", "该账号不存在！");
            return map;
        }
        if (user.getStatus() == 0) {
            map.put("usernameMsg", "该账号未激活！");
            return map;
        }
        // 验证密码
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encode = passwordEncoder.encode(password);
        if (!passwordEncoder.matches(password, encode)) {
            map.put("passwordMsg", "密码不正确！");
            return map;
        }
        return map;
    }

    /**
     * 退出登录
     *
     * @param token
     */
    public void logout(String token) {
        // 获取 SecurityContextHolder 中的用户 id
        UsernamePasswordAuthenticationToken authentication =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        Long userId = loginUser.getUser().getId();
        // 删除 redis 中的值
        redisTemplate.delete("login:" + userId);
    }

    /**
     * 查询 token
     *
     * @param token
     * @return
     */
    public LoginUser findLoginTicket(String token) {
        // 解析 token
        String userId;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token 非法");
        }
        // 从 redis 中获取用户信息
        String redisKey = "login:" + userId;
        LoginUser loginUser = JSON.parseObject(redisCache.getCacheObject(redisKey), LoginUser.class);
        return loginUser;
    }

    /**
     * 根据用户 id 查询用户信息
     *
     * @param id
     * @return
     */
    public User findUserById(Long id) {
        User user = userMapper.selectById(id);
        return user;
    }

    /**
     * 上传文件
     *
     * @param userId
     * @param headerUrl
     * @return
     */
    @Override
    public void updateHeader(Long userId, String headerUrl) {
        // 修改 redis 缓存中的信息
        String redisKey = "login:" + userId;
        LoginUser loginUser = JSON.parseObject(redisCache.getCacheObject(redisKey), LoginUser.class);
        loginUser.getUser().setHeaderUrl(headerUrl);
        redisCache.setCacheObject(redisKey, JSON.toJSONString(loginUser));
        // 修改数据库中的信息
        User user = userMapper.selectById(userId);
        user.setHeaderUrl(headerUrl);
        userMapper.updateById(user);
    }

    /**
     * 判断用户输入的原密码与数据库中的密码是否一致
     *
     * @param oldPassword 用户输入的原密码
     * @return
     */
    @Override
    public boolean getOldPassword(String oldPassword) {
        // 获取数据库中的密码
        // 获取本次请求中用户的相关信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String userId = authentication.getName();
        User user = userMapper.selectById(userId);
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        if (passwordEncoder.matches(oldPassword, user.getPassword())) {
            // 如果加密后的密码与数据库中的密码一致，则
            return true;
        }
        return false;
    }

    /**
     * 修改用户昵称
     *
     * @param nickname
     */
    @Override
    public void updateNickname(String nickname) {
        // 获取本次请求中用户的相关信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String userId = authentication.getName();
        String redisKey = "login:" + userId;
        LoginUser loginUser = JSON.parseObject(redisCache.getCacheObject(redisKey), LoginUser.class);

        // 修改 redis 缓存中的数据
        loginUser.getUser().setNickname(nickname);
        redisCache.setCacheObject(redisKey, JSON.toJSONString(loginUser));

        // 修改数据库中的数据
        User user = loginUser.getUser();
        user.setNickname(nickname);
        userMapper.updateById(user);
    }

    /**
     * 修改用户密码
     *
     * @param newPassword
     */
    @Override
    public void updatePassword(String newPassword) {
        // 获取本次请求中用户的相关信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String userId = authentication.getName();
        String redisKey = "login:" + userId;
        LoginUser loginUser = JSON.parseObject(redisCache.getCacheObject(redisKey), LoginUser.class);

        // 加密密码
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String encode = passwordEncoder.encode(newPassword);

        // 修改 redis 缓存中的数据
        loginUser.getUser().setPassword(encode);
        redisCache.setCacheObject(redisKey, JSON.toJSONString(loginUser));
        LoginUser loginUser1 = JSON.parseObject(redisCache.getCacheObject(redisKey), LoginUser.class);

        // 修改 MySQL 数据库中的数据
        User user = loginUser.getUser();
        user.setPassword(encode);
        userMapper.updateById(user);
    }
}
